Skip to content

perf_hooks: implement eventLoopUtilization() on macOS and linux - #44627

Open
sparkp1ug wants to merge 2 commits into
oven-sh:mainfrom
sparkp1ug:sparkp1ug/event-loop-utilization
Open

sparkp1ug wants to merge 2 commits into
oven-sh:mainfrom
sparkp1ug:sparkp1ug/event-loop-utilization

Conversation

@sparkp1ug

Copy link
Copy Markdown

What does this PR do?

performance.eventLoopUtilization() always returned { idle: 0, active: 0, utilization: 0 }. It now reports real numbers on macOS and Linux, following Node's semantics.

  • In us_loop_run_bun_tick, usockets adds up the time the loop spends blocked in epoll/kqueue (idle_time_ns) and records when the loop first ran (loop_start_ns). Only ticks where the loop actually sleeps are timed.
  • Two Rust host functions in node_util_binding.rs expose these values in ms. perf_hooks.ts ports Node's logic: zeros before the loop starts, elu() returns totals, elu(prev) the delta, and elu(a, b) the difference.

Not in this PR: Windows (libuv, still zeros; the docs now say so), worker.performance.eventLoopUtilization(), and the performance.nodeTiming placeholders.

How did you verify your code works?

Repro script:

import { performance } from "node:perf_hooks";
setTimeout(() => {
  const before = performance.eventLoopUtilization();
  const start = Date.now();
  while (Date.now() - start < 200) {}
  console.log("after busy work:", performance.eventLoopUtilization(before));
  const mid = performance.eventLoopUtilization();
  setTimeout(() => {
    console.log("after idle wait:", performance.eventLoopUtilization(mid));
  }, 200);
}, 10);

Before (Bun 1.4.2): both lines print { idle: 0, active: 0, utilization: 0 }.

After (debug build, macOS):

after busy work: { idle: 0, active: 199.874, utilization: 1 }
after idle wait: { idle: 200.981, active: 0.89, utilization: 0.0044 }

Node prints similar numbers for the same script.

I added a test to test/js/node/perf_hooks/perf_hooks.test.ts:

  • bun bd test test/js/node/perf_hooks/perf_hooks.test.ts: 14 pass, 0 fail.
  • USE_SYSTEM_BUN=1 bun test test/js/node/perf_hooks/perf_hooks.test.ts -t eventLoopUtilization (Bun 1.4.2): fails, because busy.active is 0.

I tested on macOS (kqueue). The Linux epoll path uses the same timing code around bun_epoll_pwait2, so CI will cover it.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@sparkp1ug

Copy link
Copy Markdown
Author

Related to #5804. Heads up: #34424 also implements eventLoopUtilization as part of a larger worker_threads change (including Windows and workers). This PR is a smaller, main-thread-only version for macOS and Linux that can land on its own. Happy to close it if you'd rather wait for #34424.

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 2f3fb56d-1b24-4c62-8870-fe4ad2d3c766
📥 Commits

Reviewing files that changed from the base of the PR and between 4118e38 and 9d086e6.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 63d7f7d3-3eff-47c0-9a9e-52c8d63359e1
📥 Commits

Reviewing files that changed from the base of the PR and between 3f1765a and 4118e38.

📒 Files selected for processing (7)
  • docs/runtime/nodejs-compat.mdx
  • packages/bun-usockets/src/eventing/epoll_kqueue.c
  • packages/bun-usockets/src/internal/loop_data.h
  • src/js/node/perf_hooks.ts
  • src/runtime/node/node_util_binding.rs
  • src/uws_sys/InternalLoopData.rs
  • test/js/node/perf_hooks/perf_hooks.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

The event loop now records start and idle times. Rust bindings expose these measurements to JavaScript, where eventLoopUtilization() calculates current values and snapshot differences. Compatibility notes specify zero values on Windows.

Changes

Event-loop utilization measurements

Layer / File(s) Summary
Record event-loop timing
packages/bun-usockets/src/internal/loop_data.h, src/uws_sys/InternalLoopData.rs, packages/bun-usockets/src/eventing/epoll_kqueue.c
Loop data stores the first tick timestamp and accumulated idle time. The tick records its start and measures time spent waiting when it is expected to idle.
Expose timing values
src/runtime/node/node_util_binding.rs, src/js/node/perf_hooks.ts
Rust functions return idle and elapsed time in milliseconds. JavaScript binds to these functions.
Calculate utilization snapshots
src/js/node/perf_hooks.ts, test/js/node/perf_hooks/perf_hooks.test.ts, docs/runtime/nodejs-compat.mdx
eventLoopUtilization() calculates totals or differences between snapshots. Tests cover busy-loop and idle measurements. Compatibility notes specify zero values on Windows.

Suggested reviewers: cirospaciari

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 4118e

The reported snapshot behavior matches Node, and no actionable issue remains after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4118e

The new API exposes aggregate timing for the caller’s event loop without accepting a target loop or granting additional privileges. No material security regression was identified. Confidence is limited by incomplete validation of concurrent access and failure paths.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Code able to execute JavaScript and load node:perf_hooks gains an aggregate activity observable for its VM’s event loop, potentially including other work sharing that loop. The inspected API does not provide a selector for another VM, process, tenant, or remote service.

Trust Boundaries and Controls

  • observed — The JavaScript-to-native transition exposes numeric measurements rather than pointers or mutable loop access. Loop identity is selected by the runtime, not by a caller-controlled argument.

Resilience and Maintainability Implications

  • observed — Timing storage starts zeroed with loop allocation and is released with the loop. Inspected wait paths preserve EINTR recovery and post-wait heap-access reacquisition, dispatch cleanup, and tick-depth decrement; the added accounting introduces no error-specific early exit that skips those steps.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the implementation of eventLoopUtilization() for macOS and Linux.
Description check ✅ Passed The description includes both required sections. It explains the change, scope, verification steps, and test results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant